Овладейте Scikit-learn Pipelines: оптимизирайте ML работни процеси, автоматизирайки предварителна обработка, обучение и настройка на хиперпараметри за здрави, възпроизводими модели.
Scikit-learn Pipeline: Пълно ръководство за автоматизация на ML работния процес
В света на машинното обучение, изграждането на модел често се представя като бляскавата последна стъпка. Въпреки това, опитните учени по данни и ML инженери знаят, че пътят към здрав модел е постлан с поредица от решаващи, често повтарящи се и податливи на грешки стъпки: почистване на данни, мащабиране на характеристики, кодиране на категориални променливи и други. Управлението на тези стъпки поотделно за тренировъчни, валидационни и тестови набори бързо може да се превърне в логистичен кошмар, водещ до фини бъгове и, най-опасно, изтичане на данни.
Тук на помощ идва Pipeline на Scikit-learn. Това не е просто удобство; това е фундаментален инструмент за изграждане на професионални, възпроизводими и готови за производство системи за машинно обучение. Това изчерпателно ръководство ще ви преведе през всичко, което трябва да знаете, за да овладеете Scikit-learn Pipelines, от основните концепции до напреднали техники.
Проблемът: Ръчният работен процес в машинното обучение
Нека разгледаме типична задача за контролирано обучение. Преди дори да можете да извикате model.fit(), трябва да подготвите данните си. Стандартен работен процес може да изглежда така:
- Разделяне на данните: Разделете набора от данни на тренировъчни и тестови набори. Това е първата и най-критична стъпка, за да гарантирате, че можете да оцените производителността на вашия модел върху невиждани данни.
- Обработка на липсващи стойности: Идентифицирайте и импутирайте липсващи данни в тренировъчния си набор (напр. използвайки средната стойност, медианата или константа).
- Кодиране на категориални характеристики: Преобразувайте нечислови колони като 'Country' или 'Product Category' в числов формат, използвайки техники като One-Hot Encoding или Ordinal Encoding.
- Мащабиране на числови характеристики: Приведете всички числови характеристики до сходен мащаб, използвайки методи като стандартизация (
StandardScaler) или нормализация (MinMaxScaler). Това е от решаващо значение за много алгоритми като SVM, логистична регресия и невронни мрежи. - Обучение на модела: Накрая, обучете избрания от вас модел за машинно обучение върху предварително обработените тренировъчни данни.
Сега, когато искате да правите предсказания върху тестовия си набор (или нови, невиждани данни), трябва да повторите абсолютно същите стъпки за предварителна обработка. Трябва да приложите същата стратегия за импутация (използвайки стойността, изчислена от тренировъчния набор), същата схема за кодиране и същите параметри за мащабиране. Ръчното проследяване на всички тези обучени трансформатори е досадно и основен източник на грешки.
Най-големият риск тук е изтичането на данни. Това се случва, когато информация от тестовия набор неволно изтече в процеса на обучение. Например, ако изчислите средната стойност за импутация или параметрите за мащабиране от целия набор от данни преди разделянето, вашият модел неявно се учи от тестовите данни. Това води до прекалено оптимистична оценка на производителността и модел, който се проваля катастрофално в реалния свят.
Представяме Scikit-learn Pipelines: Автоматизираното решение
Scikit-learn Pipeline е обект, който свързва множество стъпки за трансформация на данни и финален оценител (като класификатор или регресор) в един, унифициран обект. Можете да го разглеждате като поточна линия за вашите данни.
Когато извикате .fit() върху Pipeline, той последователно прилага fit_transform() към всяка междинна стъпка върху тренировъчните данни, подавайки изхода от една стъпка като вход за следващата. Накрая, той извиква .fit() върху последната стъпка, оценителя. Когато извикате .predict() или .transform() върху Pipeline, той прилага само метода .transform() на всяка междинна стъпка към новите данни, преди да направи предсказание с финалния оценител.
Ключови предимства от използването на Pipelines
- Предотвратяване на изтичане на данни: Това е най-критичното предимство. Като капсулирате цялата предварителна обработка в рамките на пайплайна, вие гарантирате, че трансформациите се научават само от тренировъчните данни по време на кръстосана валидация и се прилагат правилно към валидационните/тестовите данни.
- Простота и организация: Целият ви работен процес, от суровите данни до обучен модел, е кондензиран в един обект. Това прави кода ви по-чист, по-четим и по-лесен за управление.
- Възпроизводимост: Обектът Pipeline капсулира целия ви процес на моделиране. Можете лесно да запазите този единичен обект (напр. използвайки `joblib` или `pickle`) и да го заредите по-късно, за да правите предсказания, гарантирайки, че абсолютно същите стъпки се следват всеки път.
- Ефективност при търсене с решетка: Можете да извършите настройка на хиперпараметри за целия пайплайн наведнъж, намирайки най-добрите параметри както за стъпките за предварителна обработка, така и за крайния модел едновременно. Ще разгледаме тази мощна функция по-късно.
Изграждане на първия ви прост пайплайн
Нека започнем с основен пример. Представете си, че имаме числов набор от данни и искаме да мащабираме данните, преди да обучим модел за логистична регресия. Ето как бихте изградили пайплайн за това.
Първо, нека настроим нашата среда и създадем някои примерни данни.
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score
# Generate some sample data
X, y = np.random.rand(100, 5) * 10, (np.random.rand(100) > 0.5).astype(int)
# Split data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
Сега, нека дефинираме нашия пайплайн. Пайплайн се създава, като се предоставя списък от стъпки. Всяка стъпка е кортеж, съдържащ име (низ по ваш избор) и самия обект на трансформатора или оценителя.
# Create the pipeline steps
steps = [
('scaler', StandardScaler()),
('classifier', LogisticRegression())
]
# Create the Pipeline object
pipe = Pipeline(steps)
# Now, you can treat the 'pipe' object as if it were a regular model.
# Let's train it on our training data.
pipe.fit(X_train, y_train)
# Make predictions on the test data
y_pred = pipe.predict(X_test)
# Evaluate the model
accuracy = accuracy_score(y_test, y_pred)
print(f"Pipeline Accuracy: {accuracy:.4f}")
Това е! Само с няколко реда комбинирахме мащабиране и класификация. Scikit-learn обработва цялата междинна логика. Когато pipe.fit(X_train, y_train) се извика, той първо извиква StandardScaler().fit_transform(X_train) и след това предава резултата на LogisticRegression().fit(). Когато pipe.predict(X_test) се извика, той прилага вече обучения скалер, използвайки StandardScaler().transform(X_test), преди да направи предсказания с модела за логистична регресия.
Обработка на разнородни данни: ColumnTransformer
Реалните набори от данни рядко са прости. Те често съдържат комбинация от типове данни: числови колони, които се нуждаят от мащабиране, категориални колони, които се нуждаят от кодиране, и може би текстови колони, които се нуждаят от векторизация. Един прост последователен пайплайн не е достатъчен за това, тъй като трябва да прилагате различни трансформации към различни колони.
Тук ColumnTransformer блести. Той ви позволява да прилагате различни трансформатори към различни подмножества от колони във вашите данни и след това интелигентно да конкатенирате резултатите. Това е идеалният инструмент за използване като стъпка за предварителна обработка в рамките на по-голям пайплайн.
Пример: Комбиниране на числови и категориални характеристики
Нека създадем по-реалистичен набор от данни с числови и категориални характеристики, използвайки pandas.
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer
# Create a sample DataFrame
data = {
'age': [25, 30, 45, 35, 50, np.nan, 22],
'salary': [50000, 60000, 120000, 80000, 150000, 75000, 45000],
'country': ['USA', 'Canada', 'USA', 'UK', 'Canada', 'USA', 'UK'],
'purchased': [0, 1, 1, 0, 1, 1, 0]
}
df = pd.DataFrame(data)
X = df.drop('purchased', axis=1)
y = df['purchased']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Identify numerical and categorical columns
numerical_features = ['age', 'salary']
categorical_features = ['country']
Нашата стратегия за предварителна обработка ще бъде:
- За числови колони (
age,salary): Импутирайте липсващите стойности с медианата, след това ги мащабирайте. - За категориални колони (
country): Импутирайте липсващите стойности с най-честата категория, след това ги кодирайте с one-hot encoding.
Можем да дефинираме тези стъпки, използвайки два отделни мини-пайплайна.
# Create a pipeline for numerical features
numeric_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
# Create a pipeline for categorical features
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
Сега използваме `ColumnTransformer`, за да приложим тези пайплайни към правилните колони.
# Create the preprocessor with ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numerical_features),
('cat', categorical_transformer, categorical_features)
])
ColumnTransformer приема списък от `transformers`. Всеки трансформатор е кортеж, съдържащ име, обекта на трансформатора (който може да бъде и самият пайплайн) и списъка с имена на колони, към които да се приложи.
Накрая, можем да поставим този `preprocessor` като първа стъпка в нашия основен пайплайн, последван от нашия финален оценител.
from sklearn.ensemble import RandomForestClassifier
# Create the full pipeline
full_pipeline = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(random_state=42))
])
# Train and evaluate the full pipeline
full_pipeline.fit(X_train, y_train)
print("Model score on test data:", full_pipeline.score(X_test, y_test))
# You can now make predictions on new raw data
new_data = pd.DataFrame({
'age': [40, 28],
'salary': [90000, 55000],
'country': ['USA', 'Germany'] # 'Germany' is an unknown category
})
predictions = full_pipeline.predict(new_data)
print("Predictions for new data:", predictions)
Забележете колко елегантно това се справя със сложен работен процес. Параметърът `handle_unknown='ignore'` в `OneHotEncoder` е особено полезен за производствени системи, тъй като предотвратява грешки, когато се появят нови, невиждани категории в данните.
Разширени техники за Pipelines
Pipelines предлагат още повече мощност и гъвкавост. Нека разгледаме някои напреднали функции, които са от съществено значение за професионални проекти за машинно обучение.
Създаване на персонализирани трансформатори
Понякога вградените трансформатори на Scikit-learn не са достатъчни. Може да се наложи да извършите специфична за домейна трансформация, като извличане на логаритъма на характеристика или комбиниране на две характеристики в нова. Можете лесно да създадете свои собствени персонализирани трансформатори, които се интегрират безпроблемно в пайплайн.
За да направите това, създавате клас, който наследява от `BaseEstimator` и `TransformerMixin`. Трябва да имплементирате само методите `fit()` и `transform()` (и `__init__()` ако е необходимо).
Нека създадем трансформатор, който добавя нова характеристика: съотношението на `salary` към `age`.
from sklearn.base import BaseEstimator, TransformerMixin
# Define column indices (can also pass names)
age_ix, salary_ix = 0, 1
class FeatureRatioAdder(BaseEstimator, TransformerMixin):
def __init__(self):
pass # No parameters to set
def fit(self, X, y=None):
return self # Nothing to learn during fit, so just return self
def transform(self, X):
salary_age_ratio = X[:, salary_ix] / X[:, age_ix]
return np.c_[X, salary_age_ratio] # Concatenate original X with new feature
След това можете да вмъкнете този персонализиран трансформатор във вашия пайплайн за числова обработка:
numeric_transformer_with_custom = Pipeline(steps=[
('imputer', SimpleImputer(strategy='median')),
('ratio_adder', FeatureRatioAdder()), # Our custom transformer
('scaler', StandardScaler())
])
Това ниво на персонализация ви позволява да капсулирате цялата си логика за инженеринг на характеристики в рамките на пайплайна, правейки работния ви процес изключително преносим и възпроизводим.
Настройка на хиперпараметри с Pipelines, използвайки `GridSearchCV`
Това е може би едно от най-мощните приложения на Pipelines. Можете да търсите най-добрите хиперпараметри за целия си работен процес, включително стъпките за предварителна обработка и крайния модел, всичко наведнъж.
За да посочите кои параметри да настроите, използвате специален синтаксис: `step_name__parameter_name`.
Нека разширим нашия предишен пример и да настроим хиперпараметрите както за импутера в нашия препроцесор, така и за `RandomForestClassifier`.
from sklearn.model_selection import GridSearchCV
# We use the 'full_pipeline' from the ColumnTransformer example
# Define the parameter grid
param_grid = {
'preprocessor__num__imputer__strategy': ['mean', 'median'],
'classifier__n_estimators': [50, 100, 200],
'classifier__max_depth': [None, 10, 20],
'classifier__min_samples_leaf': [1, 2, 4]
}
# Create the GridSearchCV object
grid_search = GridSearchCV(full_pipeline, param_grid, cv=5, verbose=1, n_jobs=-1)
# Fit it to the data
grid_search.fit(X_train, y_train)
# Print the best parameters and score
print("Best parameters found: ", grid_search.best_params_)
print("Best cross-validation score: ", grid_search.best_score_)
# The best estimator is already refitted on the whole training data
best_model = grid_search.best_estimator_
print("Test set score with best model: ", best_model.score(X_test, y_test))
Разгледайте внимателно ключовете в `param_grid`:
'preprocessor__num__imputer__strategy': Това се отнася до параметъра `strategy` на стъпката `SimpleImputer`, наречена `imputer`, вътре в числовия пайплайн, наречен `num`, който сам по себе си е вътре в `ColumnTransformer`, наречен `preprocessor`.'classifier__n_estimators': Това се отнася до параметъра `n_estimators` на крайния оценител, наречен `classifier`.
Правейки това, `GridSearchCV` правилно изпробва всички комбинации и намира оптималния набор от параметри за целия работен процес, напълно предотвратявайки изтичането на данни по време на процеса на настройка, защото цялата предварителна обработка се извършва във всяка кръстосано валидационна сгъвка.
Визуализация и инспектиране на вашия Pipeline
Комплексните пайплайни могат да станат трудни за разбиране. Scikit-learn предоставя чудесен начин за тяхната визуализация. Започвайки от версия 0.23, можете да получите интерактивно HTML представяне.
from sklearn import set_config
# Set display to 'diagram' to get the visual representation
set_config(display='diagram')
# Now, simply displaying the pipeline object in a Jupyter Notebook or similar environment will render it
full_pipeline
Това ще генерира диаграма, която показва потока на данни през всеки трансформатор и оценител, заедно с техните имена. Това е невероятно полезно за отстраняване на грешки, споделяне на работата ви и разбиране на структурата на вашия модел.
Можете също така да получите достъп до отделни стъпки на обучен пайплайн, използвайки техните имена:
# Access the final classifier of the fitted pipeline
final_classifier = full_pipeline.named_steps['classifier']
print("Feature importances:", final_classifier.feature_importances_)
# Access the OneHotEncoder to see the learned categories
onehot_encoder = full_pipeline.named_steps['preprocessor'].named_transformers_['cat'].named_steps['onehot']
print("Categorical features learned:", onehot_encoder.categories_)
Чести клопки и най-добри практики
- Обучение върху грешни данни: Винаги, винаги обучавайте своя пайплайн САМО върху тренировъчни данни. Никога не го обучавайте върху целия набор от данни или тестовия набор. Това е основното правило за предотвратяване на изтичане на данни.
- Формати на данни: Внимавайте за формата на данните, очакван от всяка стъпка. Някои трансформатори (като тези в нашия персонализиран пример) може да работят с NumPy масиви, докато други са по-удобни с Pandas DataFrames. Scikit-learn като цяло е добър в справянето с това, но е нещо, за което трябва да сте наясно, особено при персонализирани трансформатори.
- Запазване и зареждане на Pipelines: За разгръщане на вашия модел, ще трябва да запазите обучeния пайплайн. Стандартният начин за това в екосистемата на Python е с `joblib` или `pickle`. `joblib` често е по-ефективен за обекти, които носят големи NumPy масиви.
import joblib # Save the pipeline joblib.dump(full_pipeline, 'my_model_pipeline.joblib') # Load the pipeline later loaded_pipeline = joblib.load('my_model_pipeline.joblib') # Make predictions with the loaded model loaded_pipeline.predict(new_data) - Използвайте описателни имена: Давайте на стъпките на вашия пайплайн и компонентите на `ColumnTransformer` ясни, описателни имена (напр., 'numeric_imputer', 'categorical_encoder', 'svm_classifier'). Това прави кода ви по-четим и опростява настройката на хиперпараметри и отстраняването на грешки.
Заключение: Защо Pipelines са задължителни за професионално ML
Scikit-learn Pipelines не са просто инструмент за писане на по-подреден код; те представляват промяна на парадигмата от ръчно, податливо на грешки скриптиране към систематичен, здрав и възпроизводим подход към машинното обучение. Те са гръбнакът на добрите практики за ML инженеринг.
Чрез приемането на пайплайни, вие печелите:
- Здравина: Елиминирате най-честия източник на грешки в проектите за машинно обучение – изтичането на данни.
- Ефективност: Оптимизирате целия си работен процес, от инженеринга на характеристики до настройката на хиперпараметри, в едно единно, сплотено звено.
- Възпроизводимост: Създавате единствен, сериализируем обект, който съдържа цялата ви логика на модела, което го прави лесен за разгръщане и споделяне.
Ако сте сериозни в изграждането на модели за машинно обучение, които работят надеждно в реалния свят, овладяването на Scikit-learn Pipelines не е по избор – то е от съществено значение. Започнете да ги включвате в проектите си днес и ще изграждате по-добри, по-надеждни модели по-бързо от всякога.